/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.persistence.manager.impl;

import java.io.Serializable;
import java.lang.reflect.Method;

import org.springframework.transaction.annotation.Transactional;

import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import com.genia.toolbox.basics.process.ProxyHelper;
import com.genia.toolbox.persistence.bean.CrossSessionObject;
import com.genia.toolbox.persistence.bean.Persistable;
import com.genia.toolbox.persistence.exception.ObjectIsNotPersistableException;
import com.genia.toolbox.persistence.manager.CrossSessionManager;
import com.genia.toolbox.persistence.manager.PersistenceManager;

/**
 * implementation of {@link CrossSessionManager}.
 */
public class CrossSessionManagerImpl
    implements CrossSessionManager
{
  /**
   * the interceptor used for a {@link CrossSessionObject}.
   * 
   * @param <IDENTIFIER>
   *          the type of the identifier of the persistent object
   * @param <TYPE>
   *          the type of the persistent object
   */
  private class CrossSessionMethodInterceptor<IDENTIFIER extends Serializable, TYPE extends Persistable<IDENTIFIER>>
      implements MethodInterceptor
  {

    /**
     * the identifier of the persistent object.
     */
    private transient final IDENTIFIER objectIdentifier;

    /**
     * the type of the persistent object.
     */
    private transient final Class<TYPE> persistentType;



    /**
     * constructor.
     * 
     * @param persistentType
     *          the type of the persistent object
     * @param objectIdentifier
     *          the identifier of the persistent object
     */
    @SuppressWarnings("unchecked")
    public CrossSessionMethodInterceptor(Class<?> persistentType, Serializable objectIdentifier)
    {
      this.persistentType = (Class<TYPE>) persistentType;
      this.objectIdentifier = (IDENTIFIER) objectIdentifier;
    }



    /**
     * All generated proxied methods call this method instead of the original
     * method. The original method may either be invoked by normal reflection
     * using the Method object, or by using the MethodProxy (faster).
     * 
     * @param obj
     *          "this", the enhanced object
     * @param method
     *          intercepted Method
     * @param args
     *          argument array; primitive types are wrapped
     * @param proxy
     *          used to invoke super (non-intercepted method); may be called as
     *          many times as needed
     * @throws Throwable
     *           any exception may be thrown; if so, super method will not be
     *           invoked
     * @return any value compatible with the signature of the proxied method.
     *         Method returning void will ignore this value.
     * @see MethodProxy
     * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
     *      java.lang.reflect.Method, java.lang.Object[],
     *      net.sf.cglib.proxy.MethodProxy)
     */
    @Transactional
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
        throws Throwable
    {
      Object crossSessionObject = persistenceManager.get(persistentType, objectIdentifier);
      if (method.equals(getCrossSessionObjectMethod())) {
        return crossSessionObject;
      }
      return proxy.invoke(crossSessionObject, args);
    }

  }

  /**
   * the method of the {@link CrossSessionObject} interface.
   */
  private static Method crossSessionObjectMethod;



  /**
   * getter for the method of the {@link CrossSessionObject} interface.
   * 
   * @return the method of the {@link CrossSessionObject} interface
   */
  private static Method getCrossSessionObjectMethod()
  {
    synchronized (CrossSessionManager.class) {
      if (crossSessionObjectMethod == null) {
        try {
          crossSessionObjectMethod = CrossSessionObject.class.getMethod("getCurrentObject", new Class[0]);
        }
        catch (Exception e) {
          assert (false);
          // Can't happen
        }
      }
    }
    return crossSessionObjectMethod;
  }

  /**
   * the {@link PersistenceManager} to use.
   */
  private PersistenceManager persistenceManager;



  /**
   * returns a proxy of a persistent object that can be keep across sessions.
   * 
   * @param <TYPE>
   *          the type of the generated proxy
   * @param objectToKepp
   *          the object to mimic
   * @return a proxy of a persistent object that can be keep across sessions
   * @throws ObjectIsNotPersistableException
   *           when the parameter is not {@link Persistable}.
   * @see com.genia.toolbox.persistence.manager.CrossSessionManager#getCrossSessionObject(java.lang.Class,
   *      com.genia.toolbox.persistence.bean.Persistable)
   */
  @SuppressWarnings("unchecked")
  public <TYPE> TYPE getCrossSessionObject(TYPE objectToKepp)
      throws ObjectIsNotPersistableException

  {
    if (objectToKepp == null) {
      return null;
    }
    if (!(objectToKepp instanceof Persistable)) {
      throw new ObjectIsNotPersistableException();
    }

    final Class<?> unProxiedClass = ProxyHelper.unProxy(objectToKepp.getClass());
    Factory factory = ProxyHelper.getProxyFactory(unProxiedClass, CrossSessionObject.class);
    return (TYPE) factory.newInstance(new CrossSessionMethodInterceptor(unProxiedClass, ((Persistable<?>) objectToKepp).getIdentifier()));
  }



  /**
   * getter for the persistenceManager property.
   * 
   * @return the persistenceManager
   */
  public PersistenceManager getPersistenceManager()
  {
    return persistenceManager;
  }



  /**
   * setter for the persistenceManager property.
   * 
   * @param persistenceManager
   *          the persistenceManager to set
   */
  public void setPersistenceManager(PersistenceManager persistenceManager)
  {
    this.persistenceManager = persistenceManager;
  }

}
